package ran.friend.service.user.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ran.constants.Constants;
import ran.entity.TableDataInfo;
import ran.enums.ExamType;
import ran.enums.ResultCode;
import ran.exception.ServiceException;
import ran.friend.domain.exam.dto.ExamQueryDTO;
import ran.friend.domain.exam.entity.Exam;
import ran.friend.domain.exam.vo.ExamVO;
import ran.friend.domain.user.entity.UserExam;
import ran.friend.manager.ExamRedisCacheManager;
import ran.friend.mapper.exam.ExamMapper;
import ran.friend.mapper.user.UserExamMapper;
import ran.friend.service.user.IUserExamService;
import ran.utils.ThreadLocalUtil;

import java.time.LocalDateTime;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ran
 * Date: 2025-11-14
 * Time: 19:23
 */
@Service
@Slf4j
public class UserExamServiceImpl implements IUserExamService {
    @Autowired
    private ExamMapper examMapper;
    @Autowired
    private UserExamMapper userExamMapper;
    @Autowired
    private ExamRedisCacheManager examRedisCacheManager;

    @Override
    public int enter(String token, Long examId) {
        // 先判断竞赛是否存在, 并且是已发布的
        Exam exam = examMapper.selectOne(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getExamId, examId).eq(Exam::getStatus, Constants.TRUE));
        if (exam == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        // 查看竞赛是否已经开赛
        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }

        // 获取userId, 这里发现了可能会多次解析token来获取userId, 所以我们就使用 本地线程变量 来储存用户id,相当于只需要在拦截器解析一次token,就可以存到线程变量中
        // 不同用户有不同的本地线程变量, 操作的是副本, 不会相互影响
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);

        // 查看是否重复报名
        UserExam userExam = userExamMapper.selectOne(new LambdaQueryWrapper<UserExam>()
                .eq(UserExam::getExamId, examId).eq(UserExam::getUserId, userId));
        if (userExam != null) {
            throw new ServiceException(ResultCode.USER_EXAM_HAS_ENTER);
        }
        // 存储到Redis
        examRedisCacheManager.saveUserExam(userId,examId);
        // 存储到MySQL数据库中
        userExam = new UserExam();
        userExam.setExamId(examId);
        userExam.setUserId(userId);
        return userExamMapper.insert(userExam);
    }

    @Override
    public TableDataInfo list(ExamQueryDTO examQueryDTO) {
        // 设置要获取的列表的类型
        examQueryDTO.setType(ExamType.USER_EXAM_LIST.getType());
        // 从Redis获取竞赛列表数据
        Long userId = ThreadLocalUtil.get(Constants.USER_ID,Long.class);
        Long total = examRedisCacheManager.getListSize(examQueryDTO.getType(),userId);



        List<ExamVO> examVOList;
        if (total == null || total <= 0) {
            // Redis中数据位空, 从数据库中查数据
            log.info("我的竞赛参数, examQueryDTO:{}, 用户id:{}",examQueryDTO,userId);
            PageHelper.startPage(examQueryDTO.getPageNum(),examQueryDTO.getPageSize());// 引入分页插件,可以自动在查询条件下加入分页SQL语句
            examVOList = userExamMapper.selectUserExamList(userId);
            // 刷新缓存
            examRedisCacheManager.refreshCache(examQueryDTO.getType(),userId);

            // 分压插件提供的查询数据总数的方法, 从数据库中查询
            total = new PageInfo<>(examVOList).getTotal();
        }else {
            // 从Redis中查询数据
            examVOList = examRedisCacheManager.getExamVOList(examQueryDTO,userId);
            // 再次查询数据, 这是因为在Redis查询数据时,有一旦Redis查询失败,切换成从数据库查询的逻辑
            // 这部分逻辑会再次刷新Redis中的缓存, 所以总数有可能发生变化, 这里需要再次调用, 以便获取最新值的total
            total = examRedisCacheManager.getListSize(examQueryDTO.getType(),userId);
        }
        if (CollectionUtil.isEmpty(examVOList)) {
            return TableDataInfo.empty();
        }
        return TableDataInfo.success(examVOList,total);
    }
}
